package com.hhf.feiojcodesandbox.service.impl;


import cn.hutool.core.util.ArrayUtil;
import com.github.dockerjava.api.DockerClient;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.CreateContainerCmd;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.command.ExecCreateCmdResponse;
import com.github.dockerjava.api.command.StatsCmd;
import com.github.dockerjava.api.model.*;
import com.github.dockerjava.core.command.ExecStartResultCallback;
import com.github.dockerjava.core.command.PullImageResultCallback;
import com.hhf.feiojcodesandbox.model.ExecuteMessage;
import com.hhf.feiojcodesandbox.service.CodeSandboxTemplate;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Author hhf
 * @Date 2024/10/31
 * @Description Java Docker代码沙箱
 */
@Service
@Slf4j
public class JavaDockerCodeSandbox extends CodeSandboxTemplate {

    @Resource
    private DockerClient dockerClient;

    /**
     * 执行.class文件,获取执行结果
     *
     * @param inputList
     * @param userCodeParentPath
     * @return
     * @throws IOException
     */
    @Override
    public List<ExecuteMessage> getExecuteMessageList(List<String> inputList, String userCodeParentPath) throws IOException {

        String containerId = null;

        //1. 本地docker仓库是否存在image镜像
        String image = "openjdk:8-alpine";
        List<Image> imageList = dockerClient.listImagesCmd().exec();
        List<String> allCollect = imageList
                .stream()
                .flatMap(item -> Arrays.stream(item.getRepoTags()))
                .collect(Collectors.toList());
        if (!allCollect.contains(image)) {
            log.info("从远程拉取{}镜像", image);
            // 拉取镜像
            PullImageResultCallback pullImageResultCallback = new PullImageResultCallback() {
                @Override
                public void onNext(PullResponseItem item) {
                    log.info("正在拉取远程镜像:{}", item.getStatus());
                    super.onNext(item);
                }
            };
            try {
                dockerClient.pullImageCmd(image).exec(pullImageResultCallback).awaitCompletion();
            } catch (InterruptedException e) {
                log.error("拉取镜像异常");
                throw new RuntimeException(e);
            }
        }
        log.info("本地仓库已存在{}镜像", image);

        //2. 创建能运行java的docker容器
        //2.1 创建容器
        CreateContainerCmd containerCmd = dockerClient.createContainerCmd(image);

        HostConfig hostConfig = new HostConfig();
        hostConfig.withMemory(100 * 1000 * 1000L);  //设置最大存储
        hostConfig.withMemorySwap(0L);
        hostConfig.withCpuCount(1L);                //设置cpu运行核数
        hostConfig.setBinds(new Bind(userCodeParentPath, new Volume("/app")));  //将用户代码映射到/app目录


        CreateContainerResponse response = containerCmd
                .withHostConfig(hostConfig)
                .withNetworkDisabled(true)
                .withReadonlyRootfs(true)
                .withAttachStdin(true)
                .withAttachStderr(true)
                .withAttachStdout(true)
                .withTty(true)
                .exec();

        containerId = response.getId();
        log.info("容器创建ID:{}", containerId);

        //2.2 启动容器
        dockerClient.startContainerCmd(containerId).exec();
        log.info("容器{}正在启动...", containerId);


        //3. 执行命令,并且统计执行时间和占用最大内存
        List<ExecuteMessage> executeMessageList = new ArrayList<>();

        for (String inputArgs : inputList) {

            //java -cp /app/wetqwq43214 Main 1 2
            String[] inputArgsArray = inputArgs.split(" ");


            String[] cmdArray = ArrayUtil.append(new String[]{"java", "-cp", "/app", "Main"}, inputArgsArray);


            ExecCreateCmdResponse execResponse = dockerClient.execCreateCmd(containerId)
                    .withCmd(cmdArray)
                    .withAttachStderr(true)
                    .withAttachStdin(true)
                    .withAttachStdout(true)
                    .exec();


            log.info("创建执行命令:{}", String.join(" ", cmdArray));
            String execId = execResponse.getId();



            //3.1 占用最大内存
            final long[] memory = {0L};
            StatsCmd statsCmd = dockerClient.statsCmd(containerId);

            ResultCallback<Statistics> statisticsResultCallback = statsCmd.exec(new ResultCallback<Statistics>() {
                @Override
                public void onStart(Closeable closeable) {

                }

                @Override
                public void onNext(Statistics statistics) {
                    Long usage = statistics.getMemoryStats().getUsage();
                    memory[0] = Math.max(usage, memory[0]);
                    log.info("内存占用:{}", usage);
                }

                @Override
                public void onError(Throwable throwable) {

                }

                @Override
                public void onComplete() {

                }

                @Override
                public void close() throws IOException {

                }
            });
            //开始监听内存的占用情况
            statsCmd.exec(statisticsResultCallback);
            //3.2 统计执行时间
            StopWatch stopWatch = new StopWatch();
            long time = 0L;
            final boolean[] timeout = {true};
            final String[] message = {null};
            final String[] errorMessage = {null};

            ExecStartResultCallback execStartResultCallback = new ExecStartResultCallback() {
                @Override
                public void onNext(Frame frame) {

                    StreamType type = frame.getStreamType();
                    if (StreamType.STDERR.equals(type)) {
                        errorMessage[0] = new String(frame.getPayload());
                        log.error("exec code error:{}", errorMessage[0]);
                    } else {
                        message[0] = new String(frame.getPayload()).trim();
                        log.info("exec code success:{}", message[0]);
                    }


                    super.onNext(frame);
                }

                @Override
                public void onComplete() {
                    //如果执行完成,则表示没有超时
                    timeout[0] = false;
                    super.onComplete();
                }
            };

            try {
                //开始计时
                stopWatch.start();
                dockerClient.execStartCmd(execId)
                        .exec(execStartResultCallback)
                        .awaitCompletion();
                stopWatch.stop();
                time = stopWatch.getLastTaskTimeMillis();

                log.info("结束监听内存...");
                if (timeout[0]) {
                    log.error("程序执行发生错误...");
                } else {
                    log.info("程序执行时间:{}ms", time);
                }

            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            //4. 构造响应对象
            ExecuteMessage executeMessage = new ExecuteMessage();
            executeMessage.setMessage(message[0]);
            executeMessage.setErrorMessage(errorMessage[0]);
            executeMessage.setTime(time);
            executeMessage.setMemory(memory[0]);
            executeMessageList.add(executeMessage);


        }
        //4. 移除容器
        dockerClient.removeContainerCmd(containerId).withForce(true).exec();
        log.info("容器{}移除成功",containerId);



        return executeMessageList;
    }

}
